
using System.Diagnostics;

namespace SharpTalk.Services
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.Remoting.Messaging;
    using System.ServiceModel;
    using Core;
    using Core.Delegates;
    using Core.Diagnostic;
    using Core.Enums;
    using Core.Exceptions;
    using Core.Interfaces;
    using Core.Resources;
    using Core.Services;
    using Core.Services.Contracts.Server;
    using Core.Services.Extensions;
    using Core.Services.Requests;
    using Core.Services.Responses;

    [ServiceBehavior(Namespace = "http://SharpTalk/", ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)]
    public sealed class MessageServices 
        : Traceable, IMessageServices
    {
        private static MessageServices _singleInstance;

        /// <summary>
        /// Initializes the MessageServices instance
        /// </summary>
        /// <param name="chatroomManager"></param>
        /// <param name="userManager"></param>
        /// <param name="traceProvider"></param>
        public static void Initialize(IChatRoomManager chatroomManager, IUserManager userManager, ITraceProvider traceProvider)
        {
            if (_singleInstance != null) throw new InvalidOperationException("MessageServices already initialized");
            _singleInstance = new MessageServices(chatroomManager, userManager, traceProvider);
        }
        /// <summary>
        /// Obtains the single MessageServices instance
        /// </summary>
        public static MessageServices Instance
        {
            get { return _singleInstance; }
        }

        private readonly IChatRoomManager _chatRoomManager;
        private readonly IUserManager _userManager;
        /// <summary>
        /// For internal use only
        /// </summary>
        private MessageServices()
        {
        }
        /// <summary>
        /// Creates a new instance
        /// </summary>
        /// <param name="chatroomManager"></param>
        /// <param name="userManager"></param>
        /// <exception cref="ArgumentNullException">chatroomManager or userManager is null</exception>
        private MessageServices(IChatRoomManager chatroomManager, IUserManager userManager)
        {
            if (chatroomManager == null) throw new ArgumentNullException("chatroomManager");
            if (userManager == null) throw new ArgumentNullException("userManager");

            _chatRoomManager = chatroomManager;
            _userManager = userManager;
        }

        /// <summary>
        /// Creates a new instance
        /// </summary>
        /// <param name="chatroomManager"></param>
        /// <param name="userManager"></param>
        /// <param name="traceProvider"></param>
        /// <exception cref="ArgumentNullException">chatroomManager or userManager is null</exception>
        private MessageServices(IChatRoomManager chatroomManager, IUserManager userManager, ITraceProvider traceProvider)
          : this(chatroomManager,userManager)
        {
            TraceProvider = traceProvider;
        }

        internal MessageSendResponse SendMessage(MessageSendRequest request)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            try
            {
                if(!request.IsValid()) 
                    return new MessageSendResponse(){ Content = false, Error  = new ErrorDetails(Messages.request_invalid) };

                IEnumerable<User> users = null;
                switch(request.Content.Type)
                {
                    case MessageTypeEnum.UserMessage :
                            var chatroom = _chatRoomManager.Find(request.Content.ChatRoom);
                            if(chatroom != null) 
                                users = chatroom.Users
                                    .Select(user => _userManager.Find(user))
                                    .Where(user => user != null && request.Owner != user.Guid)
                                    .ToArray();
                        break;

                    case MessageTypeEnum.UserJoin :
                    case MessageTypeEnum.UserLeft :
                            var room = _chatRoomManager.Find(request.Content.ChatRoom);
                            if(room!=null)
                                users = room.Users
                                    .Select(user => _userManager.Find(user))
                                    .Where(user => user !=null && user.Guid != ((UserNotificationMessage)request.Content.Message).Content.Guid)
                                    .ToArray();
                        break;

                    case MessageTypeEnum.UserInvite:
                        break;

                    case MessageTypeEnum.UserChange :
                            users = _userManager
                                .GetUserTrackingList(((UserNotificationMessage)request.Content.Message).Content)
                                .ToList();

                        // Notify all users in the same chatroom as the changed user if not already in the tracker list
                        // nice query isn't it ? All hail Resharper
                        foreach (var user in (from chatRoom in _chatRoomManager.All()
                                              where chatRoom.Users.Contains(((UserNotificationMessage) request.Content.Message).Content.Guid)
                                              from guid in chatRoom.Users.Where(userguid => userguid != ((UserNotificationMessage) request.Content.Message).Content.Guid).Where(guid => users.Where(u => u.Guid == guid).FirstOrDefault() == null)
                                              select guid).Select(guid => _userManager.Find(guid)).Where(user => user != null))
                        {
                            ((List<User>)users).Add( user );
                        }
                        break;

                    default :
                        throw new RequestException(Messages.request_invalid);
                }

                return users == null || !users.Any() 
                    ? new MessageSendResponse(){ Content = true }
                    : new MessageSendResponse()
                           {
                               Content = new MessageDeliveryService("clientEndpoint", this.TraceProvider).DeliverMessage(request, users)
                           };
            }
            catch(Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp, true);
                return new MessageSendResponse() {Content = false, Error = new ErrorDetails(exp)};
            }
        }

        public IAsyncResult BeginSendMessage(MessageSendRequest request, AsyncCallback callback, object state)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            var del = new RequestDelegate<MessageSendRequest, MessageSendResponse>(SendMessage);
            return del.BeginInvoke(request, callback, state);
        }
        public MessageSendResponse EndSendMessage(IAsyncResult result)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            var res = (AsyncResult)result;
            var ret = ((RequestDelegate<MessageSendRequest, MessageSendResponse>)res.AsyncDelegate).EndInvoke(result);
            res.AsyncWaitHandle.Close();
            return ret;
        }
    }
}